﻿#region Copyright(C) 2018 FI Ltd. All rights reserved.
// ==================================================================================================
// Copyright(C) 2018 FI Ltd. All rights reserved.
//
// SYSTEM NAME	    : FI.Net Application Framework
// COMPONENT ID	    : FI.Core
// COMPONENT DESC   : FI.Net Core Foundation
//
// CREATED DATE/BY  : 18 Aug 2018 / Tian Mi
//
// REVISION HISTORY :	
// DATE/BY			SR#/CS/PM#/OTHERS		DESCRIPTION OF CHANGE
// ==================================================================================================
#endregion

using FI.Core.Utilities;
using System;
using System.Reflection;
using System.Reflection.Emit;

namespace FI.Core
{
    /// <summary>
    /// A unity class used perform delegate type conversion.
    /// </summary>
    public static class EventHandlerConverter
    {
        /// <summary>
        /// Determines whether [is valid event handler] [the specified event handler type].
        /// </summary>
        /// <param name="eventHandlerType">Type of the event handler.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>
        ///   <c>true</c> if [is valid event handler] [the specified event handler type]; otherwise, <c>false</c>.
        /// </returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
        public static bool IsValidEventHandler(Type eventHandlerType, out ParameterInfo[] parameters)
        {
            Guard.ArgumentNotNull(eventHandlerType, "eventHandlerType");
            if (!typeof(Delegate).IsAssignableFrom(eventHandlerType))
            {
                parameters = new ParameterInfo[0];
                return false;
            }

            MethodInfo invokeMethod = eventHandlerType.GetMethod("Invoke");
            if (invokeMethod.ReturnType != typeof(void))
            {
                parameters = new ParameterInfo[0];
                return false;
            }
            parameters = invokeMethod.GetParameters();
            if (parameters.Length != 2 || parameters[0].ParameterType != typeof(object))
            {
                return false;
            }
            if (!typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType))
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Tries the convert.
        /// </summary>
        /// <param name="eventHandler">The event handler.</param>
        /// <param name="eventHandlerType">Type of the event handler.</param>
        /// <param name="newEventHandler">The new event handler.</param>
        /// <returns>A <see cref="bool"/> value indicating if type consersion can be performed.</returns>
        public static bool TryConvert(Delegate eventHandler, Type eventHandlerType, out Delegate newEventHandler)
        {
            Guard.ArgumentNotNull(eventHandler, "eventHandler");
            Guard.ArgumentNotNull(eventHandlerType, "eventHandlerType");

            newEventHandler = null;
            ParameterInfo[] destinationParameters;
            if (!IsValidEventHandler(eventHandlerType, out destinationParameters))
            {
                return false;
            }

            if (eventHandler.GetType() == eventHandlerType)
            {
                newEventHandler = eventHandler;
                return true;
            }

            ParameterInfo[] sourceParameters;
            if (!IsValidEventHandler(eventHandler.GetType(), out sourceParameters))
            {
                return false;
            }
            Type[] paramTypes = new Type[destinationParameters.Length + 1];
            paramTypes[0] = eventHandler.GetType();
            for (int i = 0; i < destinationParameters.Length; i++)
            {
                paramTypes[i + 1] = destinationParameters[i].ParameterType;
            }
            DynamicMethod method = new DynamicMethod("WrappedEventHandler", null, paramTypes);
            MethodInfo invoker = paramTypes[0].GetMethod("Invoke");
            ILGenerator il = method.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldarg_2);
            if (!sourceParameters[1].ParameterType.IsAssignableFrom(destinationParameters[1].ParameterType))
            {
                il.Emit(OpCodes.Castclass, sourceParameters[1].ParameterType);
            }
            il.Emit(OpCodes.Call, invoker);
            il.Emit(OpCodes.Ret);
            newEventHandler =  method.CreateDelegate(eventHandlerType, eventHandler);
            return true;
        }
    }
}
